﻿//Transaction.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Plaza
{
	using System;
	using System.Linq;

	using Ecng.Common;

	using StockSharp.BusinessEntities;
	using StockSharp.Plaza.Wrappers;

	/// <summary>
	/// Транзакция, создающая Plaza сообщения для общения с сервером РТС.
	/// </summary>
	public class Transaction
	{
		internal Transaction(Transaction originTransaction, IMessageWrapper plazaMessage)
		{
			if (originTransaction == null)
				throw new ArgumentNullException("originTransaction");

			if (plazaMessage == null)
				throw new ArgumentNullException("plazaMessage");

			if (originTransaction.IsReply)
				throw new ArgumentException("Сообщение {0} уже получило ответ.".Put(originTransaction.Id));

			originTransaction.ReplyTransaction = this;

			PlazaMessage = plazaMessage;
			OriginTransaction = originTransaction;
			Orders = originTransaction.Orders;
			Message = originTransaction.Message;
		}

		internal Transaction(IMessageWrapper plazaMessage, string message, long id, params Order[] orders)
		{
			if (plazaMessage == null)
				throw new ArgumentNullException("plazaMessage");

			PlazaMessage = plazaMessage;
			Orders = orders;
			Message = message;
			Id = id;
		}

		internal IMessageWrapper PlazaMessage { get; private set; }

		/// <summary>
		/// Заявки, ассоциированные с транзакцией.
		/// </summary>
		public Order[] Orders { get; private set; }

		/// <summary>
		/// Имя Plaza сообщения.
		/// </summary>
		public string Message { get; private set; }

		/// <summary>
		/// Уникальный идентификатор транзакции.
		/// </summary>
		public long Id { get; private set; }

		/// <summary>
		/// Является ли транзакция ответом сервера Plaza.
		/// </summary>
		public bool IsReply
		{
			get { return ReplyTransaction != null; }
		}

		/// <summary>
		/// Ответная транзакция.
		/// </summary>
		public Transaction ReplyTransaction { get; private set; }
		
		/// <summary>
		/// Исходная транзакция.
		/// </summary>
		public Transaction OriginTransaction { get; private set; }

		/// <summary>
		/// Описание ошибки.
		/// </summary>
		public PlazaException ErrorInfo { get; internal set; }

		private void CheckType(params string[] allowedMessages)
		{
			if (allowedMessages == null)
				throw new ArgumentNullException("allowedMessages");

			if (!allowedMessages.Contains(Message))
				throw new NotSupportedException("Сообщение {0} не поддерживает данный параметр.".Put(Message));
		}

		private static void CheckId(long id)
		{
			if (id <= 0)
				throw new ArgumentOutOfRangeException("id", id, "Неверное значение идентификатора.");
		}

		/// <summary>
		/// Установить направление заявки.
		/// </summary>
		/// <param name="direction">Направление заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetDirection(OrderDirections? direction)
		{
			CheckType(Messages.FutureRegister, Messages.FutureCancelGroup, Messages.OptionRegister, Messages.OptionCancelGroup);

			string name;
			int value;

			switch (Message)
			{
				case Messages.FutureRegister:
				case Messages.OptionRegister:
					if (direction == null)
						throw new ArgumentNullException("direction");

					name = "dir";
					value = direction == OrderDirections.Buy ? 1 : 2;
					break;
				case Messages.FutureCancelGroup:
				case Messages.OptionCancelGroup:
					name = "buy_sell";
					if (direction == null)
						value = 3;
					else
						value = direction == OrderDirections.Buy ? 1 : 2;
					break;
				default:
					throw new ArgumentOutOfRangeException();
			}

			return SetField(name, value);
		}


		/// <summary>
		/// Установить код инструмента в заявке.
		/// </summary>
		/// <param name="security">Инструмент.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetSecurity(Security security)
		{
			if (security == null)
				throw new ArgumentNullException("security");

			CheckType(Messages.FutureRegister, Messages.FutureCancelGroup, Messages.OptionRegister, Messages.OptionCancelGroup);

			return SetField("isin", security.ShortName);
		}

		/// <summary>
		/// Установить цену заявки.
		/// </summary>
		/// <param name="price">Цена заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetPrice(decimal price)
		{
			CheckType(Messages.FutureRegister, Messages.OptionRegister);

			return SetField("price", price.ToPlazaString());
		}

		/// <summary>
		/// Установить новую цену для первой заявки.
		/// </summary>
		/// <param name="price">Новая цена для первой заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetFirstPrice(decimal price)
		{
			CheckType(Messages.FutureReRegister, Messages.OptionReRegister);

			return SetField("price1", price.ToPlazaString());
		}

		/// <summary>
		/// Установить новую цену для второй заявки.
		/// </summary>
		/// <param name="price">Новая цена для второй заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetSecondPrice(decimal price)
		{
			CheckType(Messages.FutureReRegister, Messages.OptionReRegister);

			return SetField("price2", price.ToPlazaString());
		}

		/// <summary>
		/// Установить объем заявки.
		/// </summary>
		/// <param name="volume">Объем заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetVolume(int volume)
		{
			CheckType(Messages.FutureRegister, Messages.OptionRegister);

			return SetField("amount", volume);
		}

		/// <summary>
		/// Установить количество едениц инструмента для первой заявки.
		/// </summary>
		/// <param name="volume">Новое количество единиц инструмента для первой заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetFirstVolume(int volume)
		{
			CheckType(Messages.FutureReRegister, Messages.OptionReRegister);

			return SetField("amount1", volume);
		}

		/// <summary>
		/// Установить количество едениц инструмента для второй заявки.
		/// </summary>
		/// <param name="volume">Новое количество единиц инструмента для второй заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetSecondVolume(int volume)
		{
			CheckType(Messages.FutureReRegister, Messages.OptionReRegister);

			return SetField("amount2", volume);
		}

		/// <summary>
		/// Установить признак проверки лимитов.
		/// </summary>
		/// <param name="isCheck">Признак проверки лимитов.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetCheckLimit(bool isCheck)
		{
			CheckType(Messages.FutureRegister, Messages.OptionRegister, Messages.FutureReRegister, Messages.OptionReRegister);

			return SetField("check_limit", isCheck ? 0 : 1);
		}

		/// <summary>
		/// Установить биржевой идентификатор заявки.
		/// </summary>
		/// <param name="id">Биржевой идентификатор заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetOrderId(long id)
		{
			CheckId(id);
			CheckType(Messages.FutureCancel, Messages.OptionCancel);

			return SetField("order_id", id);
		}

		/// <summary>
		/// Установить биржевой идентификатор заявки.
		/// </summary>
		/// <param name="id">Биржевой идентификатор заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetFirstOrderId(long id)
		{
			CheckId(id);
			CheckType(Messages.FutureReRegister, Messages.OptionReRegister);

			return SetField("order_id1", id);
		}

		/// <summary>
		/// Установить биржевой идентификатор заявки.
		/// </summary>
		/// <param name="id">Биржевой идентификатор заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetSecondOrderId(long id)
		{
			CheckId(id);
			CheckType(Messages.FutureReRegister, Messages.OptionReRegister);

			return SetField("order_id2", id);
		}

		/// <summary>
		/// Установить номер транзакции заявки.
		/// </summary>
		/// <param name="id">Номер транзакции.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetTransactionId(long id)
		{
			CheckId(id);
			CheckType(Messages.FutureRegister, Messages.OptionRegister, Messages.FutureCancelGroup, Messages.OptionCancelGroup);

			return SetField("ext_id", (int)id);
		}

		/// <summary>
		/// Установить номер транзакции для первой заявки.
		/// </summary>
		/// <param name="id">Номер транзакции для первой заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetFirstTransactionId(long id)
		{
			CheckId(id);
			CheckType(Messages.FutureReRegister, Messages.OptionReRegister);

			return SetField("ext_id1", (int)id);
		}

		/// <summary>
		/// Установить номер транзакции для второй заявки.
		/// </summary>
		/// <param name="id">Номер транзакции для второй заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetSecondTransactionId(long id)
		{
			CheckId(id);
			CheckType(Messages.FutureReRegister, Messages.OptionReRegister);

			return SetField("ext_id2", (int)id);
		}

		/// <summary>
		/// Установить код клиентского счета.
		/// </summary>
		/// <param name="code">Код клиентского счета</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetClientCode(string code)
		{
			if (code.IsEmpty())
				throw new ArgumentNullException("code");

			CheckType(Messages.FutureRegister, Messages.OptionRegister, Messages.FutureCancelGroup, Messages.OptionCancelGroup, Messages.FutureChangeMoneyLimit, Messages.FutureChangeBaseAssetLimit);

			return SetField(Message == Messages.FutureRegister || Message == Messages.OptionRegister ? "client_code" : "code", code);
		}

		/// <summary>
		/// Установить режим перемещения заявок.
		/// </summary>
		/// <param name="changeVolume">Признак необходимости изменения объема заявок.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetMoveMode(bool changeVolume)
		{
			CheckType(Messages.FutureReRegister, Messages.OptionReRegister);

			return SetField("regime", changeVolume ? 0 : 1);
		}

		/// <summary>
		/// Установить вид заявки.
		/// </summary>
		/// <param name="condition">Вид заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetExecutionCondition(OrderExecutionConditions condition)
		{
			CheckType(Messages.FutureRegister, Messages.OptionRegister);

			int value;

			switch (condition)
			{
				case OrderExecutionConditions.PutInQueue:
					value = 1;
					break;
				case OrderExecutionConditions.MatchOrCancel:
					value = 3;
					break;
				case OrderExecutionConditions.CancelBalance:
					value = 2;
					break;
				default:
					throw new ArgumentOutOfRangeException("condition");
			}

			return SetField("type", value);
		}

		/// <summary>
		/// Установить признак отбора заявок.
		/// </summary>
		/// <param name="isUsual">Признак обычных заявок. Если параметр не указан будут отобраны все заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetOrderType(bool? isUsual)
		{
			CheckType(Messages.FutureCancelGroup, Messages.OptionCancelGroup);

			return SetField("non_system", isUsual == null ? 2 : (isUsual == true ? 0 : 1));
		}

		/// <summary>
		/// Установить комментарий к заявке.
		/// </summary>
		/// <param name="comment">Поле комментария. Добавляется в заявку, сделку. Может использоваться по собственному усмотрению.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetComment(string comment)
		{
			if (comment.IsEmpty())
				throw new ArgumentNullException("comment");

			if (comment.Length > 20)
				throw new ArgumentOutOfRangeException("comment", comment, "Комментарий не может быть длиннее 20 символов.");

			CheckType(Messages.FutureRegister, Messages.OptionRegister);

			return SetField("comment", comment);
		}

		/// <summary>
		/// Установить дату истечения заявки. Добавляется в заявку.
		/// </summary>
		/// <param name="expiryDate">Дата истечения заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetExpiryDate(DateTime expiryDate)
		{
			CheckType(Messages.FutureRegister, Messages.OptionRegister);

			return SetField("date_exp", expiryDate.ToPlazaString());
		}

		/// <summary>
		/// Получить биржевой идентификатор заявки.
		/// </summary>
		/// <returns>Биржевой идентификатор заявки.</returns>
		public long GetOrderId()
		{
			return (long)GetField("order_id");
		}

		/// <summary>
		/// Получить текст сообщения.
		/// </summary>
		/// <returns>Текст сообщения.</returns>
		public string GetMessage()
		{
			return (string)GetField("message");
		}

		/// <summary>
		/// Получить код возвратаю.
		/// Возможные значения:
		/// 0	успех выполнения операции
		/// Другое значение   ошибка
		/// </summary>
		/// <returns>Код возврата.</returns>
		public int GetReplyCode()
		{
			return (int)GetField("code");
		}

		/// <summary>
		/// Получить код ошибки.
		/// </summary>
		/// <returns>Код ошибки.</returns>
		public int GetErrorCode()
		{
			return (int)GetField("error_code");
		}

		/// <summary>
		/// Получить биржевой идентификатор заявки.
		/// </summary>
		/// <returns>Биржевой идентификатор заявки.</returns>
		public long GetFirstOrderId()
		{
			return (long)GetField("order_id1");
		}

		/// <summary>
		/// Получить биржевой идентификатор заявки.
		/// </summary>
		/// <returns>Биржевой идентификатор заявки.</returns>
		public long GetSecondOrderId()
		{
			return (long)GetField("order_id2");
		}

		/// <summary>
		/// Получить объем заявки.
		/// </summary>
		/// <returns>Объем заявки.</returns>
		public int GetVolume()
		{
			return (int)GetField("amount");
		}

		/// <summary>
		/// Установить количество измененных заявок в транзакции.
		/// </summary>
		/// <returns>Количество измененных заявок в транзакции.</returns>
		public int GetOrderCount()
		{
			return (int)GetField("num_orders");
		}
		
		/// <summary>
		/// Установить категорию сообщения Plaza.
		/// </summary>
		/// <param name="category">Категория сообщения Plaza.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetCategory(string category)
		{
			return SetField("P2_Category", category);
		}

		/// <summary>
		/// Получить категорию сообщения Plaza.
		/// </summary>
		/// <returns>Категория сообщения Plaza.</returns>
		public string GetCategory()
		{
			return (string)GetField("P2_Category");
		}

		/// <summary>
		/// Установить адрес получателя сообщения Plaza.
		/// </summary>
		/// <param name="address">Адрес получателя.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetAddress(string address)
		{
			//return SetField("DestAddr", address);
			PlazaMessage.DestAddr = address;
			return this;
		}

		/// <summary>
		/// Получить адрес получателя сообщения Plaza.
		/// </summary>
		/// <returns>Адрес получателя заявки.</returns>
		public string GetAddress()
		{
			return PlazaMessage.DestAddr;
		}

		/// <summary>
		/// Установить тип сообщения Plaza.
		/// </summary>
		/// <param name="type">Тип сообщения.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetMsgType(int type)
		{
			return SetField("P2_Type", type);
		}

		/// <summary>
		/// Получить тип сообщения Plaza.
		/// </summary>
		/// <returns>Тип сообщения.</returns>
		public int GetMsgType()
		{
			return (int)GetField("P2_Type");
		}

		/// <summary>
		/// Установить код РТС фирмы, которой адресована внесистемная заявка.
		/// </summary>
		/// <param name="brokerToCode">Код РТС фирмы.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetBrokerToCode(string brokerToCode)
		{
			CheckType(Messages.FutureRegister, Messages.OptionRegister);

			return SetField("broker_to", brokerToCode);
		}

		/// <summary>
		/// Установить признак, что заявка является хедж-заявкой.
		/// </summary>
		/// <param name="isHedge">Признак хэдж-заявки.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetHedge(bool isHedge)
		{
			CheckType(Messages.FutureRegister, Messages.OptionRegister);

			return SetField("hedge", (isHedge ? 1 : 0));
		}

		/// <summary>
		/// Установить признак доверительного управления. Добавляется в заявку, сделку.
		/// </summary>
		/// <param name="isTrust">Признак ДУ.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetTrust(bool isTrust)
		{
			CheckType(Messages.FutureRegister, Messages.OptionRegister);

			return SetField("du", (isTrust ? 1 : 0));
		}

		/// <summary>
		/// Установить цену для заявки по составному инструменту.
		/// Ставка - для инструментов РЕПО
		/// Своп-цена - для инструментов своп RTS Money 
		/// </summary>
		/// <param name="ratePrice">Цена для заявки по составному инструменту</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetRatePrice(decimal ratePrice)
		{
			CheckType(Messages.FutureRegister, Messages.OptionRegister);

			return SetField("rate_price", ratePrice.ToPlazaString());
		}

		/// <summary>
		/// Установить, является ли заявка заявкой РЕПО. В противном случае сообщение будет трактоваться как пара связанных заявок.
		/// </summary>
		/// <param name="isRepo">Признак заявки РЕПО.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetRepoMode(bool isRepo)
		{
			CheckType(Messages.FutureRegister, Messages.OptionRegister);
			
			return SetField("trade_mode", (isRepo ? 1 : 2));
		}

		/// <summary>
		/// Установить режим работы команды для заявок по установке лимитов/ограничений.
		/// </summary>
		/// <param name="mode">Режим работы команды.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetMode(int mode)
		{
			CheckType(Messages.FutureChangeMoneyLimit ,
				Messages.FutureChangeBaseAssetLimit,
				Messages.FutureChangeBfMoneyLimit,
				Messages.FutureChangeBfBaseAssetLimit,
				Messages.FutureChangeLimit,
				Messages.OptionChangeLimit);

			return SetField("mode", mode);
		}

		/// <summary>
		/// Установить произвольное поле сообщения Plaza.
		/// </summary>
		/// <param name="name">Имя устанавливаемого поля.</param>
		/// <param name="value">Значение устанавливаемого поля.</param>
		/// <returns>Транзакция.</returns>
		public Transaction SetField(string name, object value)
		{
			if (name.IsEmpty())
				throw new ArgumentNullException("name");

			if (value == null)
				throw new ArgumentNullException("value");

			PlazaMessage.SetField(name, value);
			return this;
		}

		/// <summary>
		/// Получить произвольное поле сообщения Plaza.
		/// </summary>
		/// <param name="name">Имя получаемого поля.</param>
		/// <returns>Транзакция.</returns>
		public object GetField(string name)
		{
			if (name.IsEmpty())
				throw new ArgumentNullException("name");

			return PlazaMessage.GetField(name);
		}

		/// <summary>
		/// Проверить содержит ли транзакция нужное число заявок.
		/// </summary>
		internal void CheckOrdersLength()
		{
			bool isOk;

			switch (Message)
			{
				case Messages.FutureRegister:
				case Messages.OptionRegister:
				case Messages.FutureCancel:
				case Messages.OptionCancel:
					isOk = Orders.Length == 1;
					break;
				case Messages.FutureReRegister:
				case Messages.OptionReRegister:
					isOk = Orders.Length == 2 || Orders.Length == 4;
					break;
				case Messages.FutureCancelGroup:
				case Messages.OptionCancelGroup:
					isOk = true;
					break;
				default:
					throw new ArgumentOutOfRangeException();
			}

			if (!isOk)
				throw new InvalidOperationException("Неверное число заявок {0} в транзакции {1}.".Put(Orders.Length, Message));
		}
	}
}